SQL Injection
略称: SQLi
ユーザーの入力をSQLクエリ中に注入(Inject)できてしまう脆弱性のこと ログインなどを不正に突破できたり、データベース内の情報を窃取できたりする
例えば、次のような問題を考える
ログインフォームが与えられる (username、passwordが入力できる)
adminとしてログインすることが目標
ログイン処理は以下のようになっている
code:vulnable.py
def login_user(mysql_connection, username, password):
cur = mysql_connection.cursor()
res = cur.execute("SELECT * FROM user WHERE name='" + username + "' AND pass='" + password + "'")
logged_user = res.fetchone()
return logged_user is not None
username, passwordの内容がそのままSQLクエリに反映されていることに注目する
usernameがadmin、passwordが' OR 1=1 -- のとき、クエリは以下のようになる
SELECT * FROM user WHERE name='admin' AND pass='' OR 1=1 -- '
1=1が真なので、WHEREの条件文は常に真となる
MySQLでは-- はコメントを表すので、それ以降は無視される
つまり、このクエリを送るとSELECTが正常にヒットする
関数はSELECT文が値を返すかどうかでログイン可否を検証しているので、これでadminのパスワードを知らなくてもログインができた
対策
プリペアドステートメント / 静的プレースホルダ を利用する
RDBMSに安全に値を渡せる仕組み
以下のようにするとプリペアドステートメントが使用される
code:safe.py
cur.execute("SELECT * FROM user WHERE name=? AND pass=?", (username, password))
適切なエスケープを行う
絶対にエスケープ処理を自分で実装しないでライブラリを使ってください
あまりおすすめしません
RDBMSごとに構文の違いがあるので、攻撃するときはチートシートを参考にするといいかも
テクニック
INSERT句でSQLiできる場合
INSERT INTO comment VALUES ('$user', '$msg')のようなクエリがあるとする (コメントの追加)
user, msgの内容はWebページから見れるとする
この場合はサブクエリが使える
$userに適当な値、$msgに'||(SELECT flag FROM secret)||'を入れるとこうなる
INSERT INTO comment VALUES ('hoge', ''||(SELECT flag FROM secret))||'')
SELECT句のサブクエリを使ってmsgにflagの中身を連結させている
結果的にコメントにフラグが出力される
対話型シェルでSQLを実行している場合
複文実行で.shellなどのコマンドが使える可能性がある
LIMIT句
LIMIT a,bでb番目から始まるa行分の結果を返す
1行しか見れない場合に、LIMIT 1,iで1行ずつ見る
GROUP_CONCAT関数
複数の行をひとつにまとめられる
かなり楽できる
WAF Bypass
'が制限されているとき
\を使うとbypassできる場合がある
NULL文字を入れればクエリを強制的に終了できるというテクがあった気がするが、元のWriteupを思い出せない
心当たりある人は教えてください
LIMITやORDER BYの後にInjectionできる場合
資料